/*
 * Copyright (C) 2014, Pavel Samko <bulldozerbsg@gmail.com>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the "Pavel Samko" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

#define INITGUID

#include <windows.h>
#include <windowsx.h>
#include <shobjidl.h>
#include <stdlib.h>
#include "pun_guid.h"
#include "pun_res.h"

#ifdef DEBUG
#include <stdio.h>
#include <stdarg.h>
void debug_printf(char *format, ...)
{
	va_list args;
	va_start(args, format);
	char buff[512];
	vsnprintf(buff, sizeof(buff), format, args);
	OutputDebugStringA(buff);
	va_end(args);
}
#define LOG debug_printf
#else
#define LOG(...) do {} while(0)
#endif

#define CHECKBOXSTATE_BY_INVALID_VALUE 0x0003

#define SET_CHECKBOXSTATE_BY_FILEATTR(checkBoxState, fileAttr, fileAttrFlag) \
if (fileAttr & fileAttrFlag) \
{ \
	if (checkBoxState != BST_CHECKED) \
	{ \
		checkBoxState = (checkBoxState == 0x0003)? BST_CHECKED : BST_INDETERMINATE; \
	} \
} else \
{ \
	if (checkBoxState != BST_UNCHECKED) \
	{ \
		checkBoxState = (checkBoxState == 0x0003)? BST_UNCHECKED : BST_INDETERMINATE; \
	} \
}

UINT g_lock = 0;
UINT g_objCount = 0;
HINSTANCE g_instDLL = NULL;

// IPunShellExtInit, IPunShellPropSheetExt

typedef struct IPunShellExtInit IPunShellExtInit;
typedef struct IPunShellPropSheetExt IPunShellPropSheetExt;

typedef struct IPunShellExtInitVtbl
{
	// IUnknown
	HRESULT STDMETHODCALLTYPE (*QueryInterface)(IPunShellExtInit *, REFIID, LPVOID *);
	ULONG STDMETHODCALLTYPE (*AddRef)(IPunShellExtInit *);
	ULONG STDMETHODCALLTYPE (*Release)(IPunShellExtInit *);
	// IShellExtInit
	HRESULT STDMETHODCALLTYPE (*Initialize)(IPunShellExtInit *, PCIDLIST_ABSOLUTE, IDataObject *, HKEY);
} IPunShellExtInitVtbl;

typedef struct IPunShellPropSheetExtVtbl
{
	// IUnknown
	HRESULT STDMETHODCALLTYPE (*QueryInterface)(IPunShellPropSheetExt *, REFIID, LPVOID *);
	ULONG STDMETHODCALLTYPE (*AddRef)(IPunShellPropSheetExt *);
	ULONG STDMETHODCALLTYPE (*Release)(IPunShellPropSheetExt *);
	// IShellPropSheetExt
	HRESULT STDMETHODCALLTYPE (*AddPages)(IPunShellPropSheetExt *, LPFNSVADDPROPSHEETPAGE, LPARAM);
	HRESULT STDMETHODCALLTYPE (*ReplacePage)(IPunShellPropSheetExt *, EXPPS uPageID, LPFNSVADDPROPSHEETPAGE, LPARAM);
} IPunShellPropSheetExtVtbl;

struct IPunShellPropSheetExt
{
	IPunShellPropSheetExtVtbl *lpVtbl;
	IPunShellExtInit *m_parent;
};

struct IPunShellExtInit
{
	IPunShellExtInitVtbl *lpVtbl;
	UINT m_count;
	IPunShellPropSheetExt m_shellPropSheetExt;
	IDataObject *m_dataObject;

	WORD m_archiveState;
	BOOL m_archiveStateNeedChange;
	WORD m_readonlyState;
	BOOL m_readonlyStateNeedChange;
	WORD m_hiddenState;
	BOOL m_hiddenStateNeedChange;
	WORD m_systemState;
	BOOL m_systemStateNeedChange;
	SYSTEMTIME m_creationTime;
	BOOL m_creationTimeNeedChange;
	SYSTEMTIME m_creationDate;
	BOOL m_creationDateNeedChange;
	SYSTEMTIME m_lastAccessTime;
	BOOL m_lastAccessTimeNeedChange;
	SYSTEMTIME m_lastAccessDate;
	BOOL m_lastAccessDateNeedChange;
	SYSTEMTIME m_lastWriteTime;
	BOOL m_lastWriteTimeNeedChange;
	SYSTEMTIME m_lastWriteDate;
	BOOL m_lastWriteDateNeedChange;
};

void GetFileAttributesFromIDataObject(IPunShellExtInit *pShellExtInit)
{
	IDataObject *pdtobj = pShellExtInit->m_dataObject;
	STGMEDIUM medium;
	FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
	UINT uCount = 0;
	if (pdtobj && SUCCEEDED(pdtobj->lpVtbl->GetData(pdtobj, &fe, &medium)))
	{
		uCount = DragQueryFileW((HDROP)medium.hGlobal, (UINT)-1, NULL, 0);
		for (UINT i = 0; i < uCount; ++i)
		{
			UINT l = DragQueryFileW((HDROP)medium.hGlobal, i, NULL, 0);
			wchar_t *fileName;
			if (l && (fileName = (wchar_t *)malloc((l + 1) * sizeof(wchar_t))))
			{
				DragQueryFileW((HDROP)medium.hGlobal, i, fileName, l + 1);
				WIN32_FILE_ATTRIBUTE_DATA attr;
				if (GetFileAttributesExW(fileName, GetFileExInfoStandard, &attr))
				{
					SET_CHECKBOXSTATE_BY_FILEATTR(pShellExtInit->m_archiveState, attr.dwFileAttributes, FILE_ATTRIBUTE_ARCHIVE)
					SET_CHECKBOXSTATE_BY_FILEATTR(pShellExtInit->m_readonlyState, attr.dwFileAttributes, FILE_ATTRIBUTE_READONLY)
					SET_CHECKBOXSTATE_BY_FILEATTR(pShellExtInit->m_hiddenState, attr.dwFileAttributes, FILE_ATTRIBUTE_HIDDEN)
					SET_CHECKBOXSTATE_BY_FILEATTR(pShellExtInit->m_systemState, attr.dwFileAttributes, FILE_ATTRIBUTE_SYSTEM)
				}
				if (uCount == 1)
				{
					SYSTEMTIME utcTime;
					FileTimeToSystemTime(&attr.ftCreationTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &pShellExtInit->m_creationTime);
					memcpy(&pShellExtInit->m_creationDate, &pShellExtInit->m_creationTime, sizeof(SYSTEMTIME));
					FileTimeToSystemTime(&attr.ftLastAccessTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &pShellExtInit->m_lastAccessTime);
					memcpy(&pShellExtInit->m_lastAccessDate, &pShellExtInit->m_lastAccessTime, sizeof(SYSTEMTIME));
					FileTimeToSystemTime(&attr.ftLastWriteTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &pShellExtInit->m_lastWriteTime);
					memcpy(&pShellExtInit->m_lastWriteDate, &pShellExtInit->m_lastWriteTime, sizeof(SYSTEMTIME));
				}
				free(fileName);
			}
		}
		ReleaseStgMedium(&medium);
	}
}

inline void _CopyTime(SYSTEMTIME *dst, const SYSTEMTIME *src)
{
	dst->wHour = src->wHour;
	dst->wMinute = src->wMinute;
	dst->wSecond = src->wSecond;
	dst->wMilliseconds = src->wMilliseconds;
}

inline void _CopyDate(SYSTEMTIME *dst, const SYSTEMTIME *src)
{
	dst->wYear = src->wYear;
	dst->wMonth = src->wMonth;
	dst->wDay = src->wDay;
	dst->wDayOfWeek = src->wDayOfWeek;
}

void SetFileAttributesToIDataObject(IPunShellExtInit *pShellExtInit)
{
	IDataObject *pdtobj = pShellExtInit->m_dataObject;
	STGMEDIUM medium;
	FORMATETC fe = {CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
	UINT uCount = 0;
	if (pdtobj && SUCCEEDED(pdtobj->lpVtbl->GetData(pdtobj, &fe, &medium)))
	{
		uCount = DragQueryFileW((HDROP)medium.hGlobal, (UINT)-1, NULL, 0);
		for (UINT i = 0; i < uCount; ++i)
		{
			UINT l = DragQueryFileW((HDROP)medium.hGlobal, i, NULL, 0);
			wchar_t *fileName;
			if (l && (fileName = (wchar_t *)malloc((l + 1) * sizeof(wchar_t))))
			{
				DragQueryFileW((HDROP)medium.hGlobal, i, fileName, l + 1);
				WIN32_FILE_ATTRIBUTE_DATA attr;
				SYSTEMTIME localTime;
				SYSTEMTIME utcTime;
				if (GetFileAttributesExW(fileName, GetFileExInfoStandard, &attr))
				{
					// Set Times
					BOOL needSetTimes = FALSE;
					// Creation Time
					FileTimeToSystemTime(&attr.ftCreationTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &localTime);
					if (pShellExtInit->m_creationTimeNeedChange)
					{
						_CopyTime(&localTime, &pShellExtInit->m_creationTime);
					}
					if (pShellExtInit->m_creationDateNeedChange)
					{
						_CopyDate(&localTime, &pShellExtInit->m_creationDate);
					}
					if (pShellExtInit->m_creationTimeNeedChange || pShellExtInit->m_creationDateNeedChange)
					{
						TzSpecificLocalTimeToSystemTime(NULL, &localTime, &utcTime);
						SystemTimeToFileTime(&utcTime, &attr.ftCreationTime);
						needSetTimes = TRUE;
					}
					// Last Access Time
					FileTimeToSystemTime(&attr.ftLastAccessTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &localTime);
					if (pShellExtInit->m_lastAccessTimeNeedChange)
					{
						_CopyTime(&localTime, &pShellExtInit->m_lastAccessTime);
					}
					if (pShellExtInit->m_lastAccessDateNeedChange)
					{
						_CopyDate(&localTime, &pShellExtInit->m_lastAccessDate);
					}
					if (pShellExtInit->m_lastAccessTimeNeedChange || pShellExtInit->m_lastAccessDateNeedChange)
					{
						TzSpecificLocalTimeToSystemTime(NULL, &localTime, &utcTime);
						SystemTimeToFileTime(&utcTime, &attr.ftLastAccessTime);
						needSetTimes = TRUE;
					}
					// Last Write Time
					FileTimeToSystemTime(&attr.ftLastWriteTime, &utcTime);
					SystemTimeToTzSpecificLocalTime(NULL, &utcTime, &localTime);
					if (pShellExtInit->m_lastWriteTimeNeedChange)
					{
						_CopyTime(&localTime, &pShellExtInit->m_lastWriteTime);
					}
					if (pShellExtInit->m_lastWriteDateNeedChange)
					{
						_CopyDate(&localTime, &pShellExtInit->m_lastWriteDate);
					}
					if (pShellExtInit->m_lastWriteTimeNeedChange || pShellExtInit->m_lastWriteDateNeedChange)
					{
						TzSpecificLocalTimeToSystemTime(NULL, &localTime, &utcTime);
						SystemTimeToFileTime(&utcTime, &attr.ftLastWriteTime);
						needSetTimes = TRUE;
					}

					if (needSetTimes)
					{
						HANDLE file = CreateFileW(fileName, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
						if (file != INVALID_HANDLE_VALUE)
						{
							SetFileTime(file,
								(pShellExtInit->m_creationTimeNeedChange || pShellExtInit->m_creationDateNeedChange)? &attr.ftCreationTime : NULL,
								(pShellExtInit->m_lastAccessTimeNeedChange || pShellExtInit->m_lastAccessDateNeedChange)? &attr.ftLastAccessTime : NULL,
								(pShellExtInit->m_lastWriteTimeNeedChange || pShellExtInit->m_lastWriteDateNeedChange)? &attr.ftLastWriteTime : NULL);
							CloseHandle(file);
						}
					}
					// Set Attributes
					BOOL needSetAttributes = FALSE;
					// Archive Attribute
					if (pShellExtInit->m_archiveStateNeedChange)
					{
						if ((attr.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) && pShellExtInit->m_archiveState == BST_UNCHECKED)
						{
							attr.dwFileAttributes &= ~FILE_ATTRIBUTE_ARCHIVE;
							needSetAttributes = TRUE;
						} else
						if (!(attr.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) && pShellExtInit->m_archiveState == BST_CHECKED)
						{
							attr.dwFileAttributes |= FILE_ATTRIBUTE_ARCHIVE;
							needSetAttributes = TRUE;
						}
					}
					// Read Only Attribute
					if (pShellExtInit->m_readonlyStateNeedChange)
					{
						if ((attr.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && pShellExtInit->m_readonlyState == BST_UNCHECKED)
						{
							attr.dwFileAttributes &= ~FILE_ATTRIBUTE_READONLY;
							needSetAttributes = TRUE;
						} else
						if (!(attr.dwFileAttributes & FILE_ATTRIBUTE_READONLY) && pShellExtInit->m_readonlyState == BST_CHECKED)
						{
							attr.dwFileAttributes |= FILE_ATTRIBUTE_READONLY;
							needSetAttributes = TRUE;
						}
					}
					// Hidden Attribute
					if (pShellExtInit->m_hiddenStateNeedChange)
					{
						if ((attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && pShellExtInit->m_hiddenState == BST_UNCHECKED)
						{
							attr.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
							needSetAttributes = TRUE;
						} else
						if (!(attr.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && pShellExtInit->m_hiddenState == BST_CHECKED)
						{
							attr.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
							needSetAttributes = TRUE;
						}
					}
					// System Attribute
					if (pShellExtInit->m_systemStateNeedChange)
					{
						if ((attr.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) && pShellExtInit->m_systemState == BST_UNCHECKED)
						{
							attr.dwFileAttributes &= ~FILE_ATTRIBUTE_SYSTEM;
							needSetAttributes = TRUE;
						} else
						if (!(attr.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) && pShellExtInit->m_systemState == BST_CHECKED)
						{
							attr.dwFileAttributes |= FILE_ATTRIBUTE_SYSTEM;
							needSetAttributes = TRUE;
						}
					}

					if (needSetAttributes)
					{
						SetFileAttributesW(fileName, attr.dwFileAttributes);
					}
				}
				free(fileName);
			}
		}
		ReleaseStgMedium(&medium);
	}
}

INT_PTR CALLBACK AttrPropSheetProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	switch(uMsg)
	{
		case WM_INITDIALOG:
		{
			LPPROPSHEETPAGEW ppsp = (LPPROPSHEETPAGEW)lParam;
			IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)ppsp->lParam;

			SetLastError(ERROR_SUCCESS);
			SetWindowLongPtr(hDlg, GWLP_USERDATA, (LONG_PTR)pShellExtInit);

			GetFileAttributesFromIDataObject(pShellExtInit);

			if (pShellExtInit->m_archiveState != CHECKBOXSTATE_BY_INVALID_VALUE) CheckDlgButton(hDlg, IDC_ATTRPROPSHEET_ARCHIVE_CHECKBOX, pShellExtInit->m_archiveState);
			if (pShellExtInit->m_readonlyState != CHECKBOXSTATE_BY_INVALID_VALUE) CheckDlgButton(hDlg, IDC_ATTRPROPSHEET_READONLY_CHECKBOX, pShellExtInit->m_readonlyState);
			if (pShellExtInit->m_hiddenState != CHECKBOXSTATE_BY_INVALID_VALUE) CheckDlgButton(hDlg, IDC_ATTRPROPSHEET_HIDDEN_CHECKBOX, pShellExtInit->m_hiddenState);
			if (pShellExtInit->m_systemState != CHECKBOXSTATE_BY_INVALID_VALUE) CheckDlgButton(hDlg, IDC_ATTRPROPSHEET_SYSTEM_CHECKBOX, pShellExtInit->m_systemState);

			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_TIME), GDT_VALID, &pShellExtInit->m_creationTime);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_DATE), GDT_VALID, &pShellExtInit->m_creationTime);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_TIME), GDT_VALID, &pShellExtInit->m_lastAccessTime);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_DATE), GDT_VALID, &pShellExtInit->m_lastAccessTime);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_TIME), GDT_VALID, &pShellExtInit->m_lastWriteTime);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_DATE), GDT_VALID, &pShellExtInit->m_lastWriteTime);

			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_TIME), GDT_NONE, 0);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_DATE), GDT_NONE, 0);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_TIME), GDT_NONE, 0);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_DATE), GDT_NONE, 0);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_TIME), GDT_NONE, 0);
			DateTime_SetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_DATE), GDT_NONE, 0);
		}
		return TRUE;

		case WM_COMMAND:
		{
			switch (LOWORD(wParam))
			{
				case IDC_ATTRPROPSHEET_ARCHIVE_CHECKBOX:
				{
					IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
					pShellExtInit->m_archiveStateNeedChange = TRUE;
					PropSheet_Changed(GetParent(hDlg), hDlg);
					if (IsDlgButtonChecked(hDlg, LOWORD(wParam)) != BST_UNCHECKED)
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_UNCHECKED);
					} else
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_CHECKED);
					}
				}
				return TRUE;

				case IDC_ATTRPROPSHEET_READONLY_CHECKBOX:
				{
					IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
					pShellExtInit->m_readonlyStateNeedChange = TRUE;
					PropSheet_Changed(GetParent(hDlg), hDlg);
					if (IsDlgButtonChecked(hDlg, LOWORD(wParam)) != BST_UNCHECKED)
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_UNCHECKED);
					} else
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_CHECKED);
					}
				}
				return TRUE;

				case IDC_ATTRPROPSHEET_HIDDEN_CHECKBOX:
				{
					IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
					pShellExtInit->m_hiddenStateNeedChange = TRUE;
					PropSheet_Changed(GetParent(hDlg), hDlg);
					if (IsDlgButtonChecked(hDlg, LOWORD(wParam)) != BST_UNCHECKED)
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_UNCHECKED);
					} else
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_CHECKED);
					}
				}
				return TRUE;

				case IDC_ATTRPROPSHEET_SYSTEM_CHECKBOX:
				{
					IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)GetWindowLongPtr(hDlg, GWLP_USERDATA);
					pShellExtInit->m_systemStateNeedChange = TRUE;
					PropSheet_Changed(GetParent(hDlg), hDlg);
					if (IsDlgButtonChecked(hDlg, LOWORD(wParam)) != BST_UNCHECKED)
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_UNCHECKED);
					} else
					{
						CheckDlgButton(hDlg, LOWORD(wParam), BST_CHECKED);
					}
				}
				return TRUE;

				case IDC_ATTRPROPSHEET_CREATION_TIME:
				case IDC_ATTRPROPSHEET_CREATION_DATE:
				case IDC_ATTRPROPSHEET_LASTACCESS_TIME:
				case IDC_ATTRPROPSHEET_LASTACCESS_DATE:
				case IDC_ATTRPROPSHEET_LASTWRITE_TIME:
				case IDC_ATTRPROPSHEET_LASTWRITE_DATE:
				{
					
				}
				return TRUE;
			}
		}
		break;

		case WM_NOTIFY:
		{
			LPPSHNOTIFY ppsn = (LPPSHNOTIFY)lParam;
			switch (ppsn->hdr.code)
			{
				case DTN_DATETIMECHANGE:
				{
					PropSheet_Changed(GetParent(hDlg), hDlg);
				}
				return 0;

				case PSN_APPLY:
				{
					IPunShellExtInit *pShellExtInit = (IPunShellExtInit *)GetWindowLongPtr(hDlg, GWLP_USERDATA);

					pShellExtInit->m_creationTimeNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_TIME), &pShellExtInit->m_creationTime) == GDT_VALID) ? TRUE : FALSE;
					pShellExtInit->m_creationDateNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_CREATION_DATE), &pShellExtInit->m_creationDate) == GDT_VALID) ? TRUE : FALSE;
					pShellExtInit->m_lastAccessTimeNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_TIME), &pShellExtInit->m_lastAccessTime) == GDT_VALID) ? TRUE : FALSE;
					pShellExtInit->m_lastAccessDateNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTACCESS_DATE), &pShellExtInit->m_lastAccessDate) == GDT_VALID) ? TRUE : FALSE;
					pShellExtInit->m_lastWriteTimeNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_TIME), &pShellExtInit->m_lastWriteTime) == GDT_VALID) ? TRUE : FALSE;
					pShellExtInit->m_lastWriteDateNeedChange = (DateTime_GetSystemtime(GetDlgItem(hDlg, IDC_ATTRPROPSHEET_LASTWRITE_DATE), &pShellExtInit->m_lastWriteDate) == GDT_VALID) ? TRUE : FALSE;

					if (pShellExtInit->m_archiveStateNeedChange)
					{
						pShellExtInit->m_archiveState = IsDlgButtonChecked(hDlg, IDC_ATTRPROPSHEET_ARCHIVE_CHECKBOX);
					}
					if (pShellExtInit->m_readonlyStateNeedChange)
					{
						pShellExtInit->m_readonlyState = IsDlgButtonChecked(hDlg, IDC_ATTRPROPSHEET_READONLY_CHECKBOX);
					}
					if (pShellExtInit->m_hiddenStateNeedChange)
					{
						pShellExtInit->m_hiddenState = IsDlgButtonChecked(hDlg, IDC_ATTRPROPSHEET_HIDDEN_CHECKBOX);
					}
					if (pShellExtInit->m_systemStateNeedChange)
					{
						pShellExtInit->m_systemState = IsDlgButtonChecked(hDlg, IDC_ATTRPROPSHEET_SYSTEM_CHECKBOX);
					}

					SetFileAttributesToIDataObject(pShellExtInit);
				}
				return PSNRET_NOERROR;
			}
		}
		break;

		case WM_CTLCOLORDLG:
		case WM_CTLCOLORSTATIC:
		case WM_CTLCOLORBTN:
		return (INT_PTR)GetStockObject(WHITE_BRUSH);
	}
	return FALSE;
}

UINT CALLBACK AttrCallback(HWND hWnd, UINT uMsg, LPPROPSHEETPAGEW ppsp)
{
	UNREFERENCED_PARAMETER(hWnd);
	switch (uMsg)
	{
		case PSPCB_CREATE:
		return TRUE;

		case PSPCB_RELEASE:
		{
			IPunShellExtInit *shellExtInit = (IPunShellExtInit *)ppsp->lParam;
			shellExtInit->lpVtbl->Release(shellExtInit);
		}
		break;
	}
	return FALSE;
}

HRESULT STDMETHODCALLTYPE pseiQueryInterface(IPunShellExtInit *this, REFIID riid, LPVOID *ppv)
{
	HRESULT result;
	if (!ppv)
	{
		result = E_POINTER;
	} else
	if (IsEqualIID(riid, &IID_IUnknown) ||
		IsEqualIID(riid, &IID_IShellExtInit))
	{
		*ppv = this;
		this->lpVtbl->AddRef(this);
		result = S_OK;
	} else
	if (IsEqualIID(riid, &IID_IShellPropSheetExt))
	{
		*ppv = &this->m_shellPropSheetExt;
		this->lpVtbl->AddRef(this);
		result = S_OK;
	}
	else
	{
		*ppv = NULL;
		result = E_NOINTERFACE;
	}
	return result;
}

ULONG STDMETHODCALLTYPE pseiAddRef(IPunShellExtInit *this)
{
	ULONG result = ++this->m_count;
	return result;
}

ULONG STDMETHODCALLTYPE pseiRelease(IPunShellExtInit *this)
{
	ULONG result = --this->m_count;
	if (!result)
	{
		if (this->m_dataObject)
		{
			this->m_dataObject->lpVtbl->Release(this->m_dataObject);
			this->m_dataObject = NULL;
		}
		free(this);
		--g_objCount;
	}
	return result;
}

HRESULT STDMETHODCALLTYPE pseiInitialize(IPunShellExtInit *this, PCIDLIST_ABSOLUTE pidlFolder, IDataObject *pdtobj, HKEY hkeyProgID)
{
	UNREFERENCED_PARAMETER(pidlFolder);
	UNREFERENCED_PARAMETER(hkeyProgID);
	// Initialize can be called more than once.
	if (this->m_dataObject)
	{
		this->m_dataObject->lpVtbl->Release(this->m_dataObject);
		this->m_dataObject = NULL;
	}
	
	if (!pdtobj)
	{
		return E_INVALIDARG;
	}

	this->m_dataObject = pdtobj;
	pdtobj->lpVtbl->AddRef(pdtobj);

	return S_OK;
}

IPunShellExtInitVtbl g_pseiVtbl = 
{
	pseiQueryInterface,
	pseiAddRef,
	pseiRelease,
	pseiInitialize
};

HRESULT STDMETHODCALLTYPE pspeQueryInterface(IPunShellPropSheetExt *this, REFIID riid, LPVOID *ppv)
{
	HRESULT result;
	if (!ppv)
	{
		result = E_POINTER;
	} else
	if (IsEqualIID(riid, &IID_IUnknown) ||
		IsEqualIID(riid, &IID_IShellExtInit))
	{
		*ppv = this->m_parent;
		this->lpVtbl->AddRef(this);
		result = S_OK;
	} else
	if (IsEqualIID(riid, &IID_IShellPropSheetExt))
	{
		*ppv = this;
		this->lpVtbl->AddRef(this);
		result = S_OK;
	}
	else
	{
		*ppv = NULL;
		result = E_NOINTERFACE;
	}
	return result;
}

ULONG STDMETHODCALLTYPE pspeAddRef(IPunShellPropSheetExt *this)
{
	ULONG result = this->m_parent->lpVtbl->AddRef(this->m_parent);
	return result;
}

ULONG STDMETHODCALLTYPE pspeRelease(IPunShellPropSheetExt *this)
{
	ULONG result = this->m_parent->lpVtbl->Release(this->m_parent);
	return result;
}

HRESULT STDMETHODCALLTYPE pspeAddPages(IPunShellPropSheetExt *this, LPFNSVADDPROPSHEETPAGE pfnAddPage, LPARAM lParam)
{
	IPunShellExtInit *shellExtInit = this->m_parent;
	if (!pfnAddPage)
	{
		return E_INVALIDARG;
	}

	PROPSHEETPAGEW psp = {0};
	psp.dwSize = sizeof(psp);
	psp.dwFlags = PSP_DEFAULT | PSP_USEREFPARENT | PSP_USETITLE | PSP_USECALLBACK;
	psp.hInstance = g_instDLL;
	psp.pszTemplate = MAKEINTRESOURCEW(IDC_ATTRPROPSHEET_PAGE);
	psp.pfnDlgProc = AttrPropSheetProc;
	psp.pcRefParent = &g_objCount;
	psp.pfnCallback = AttrCallback;
	psp.lParam = (LPARAM)shellExtInit;
	psp.pszTitle = MAKEINTRESOURCEW(IDS_ATTRPROPSHEET_TITLE);

	HPROPSHEETPAGE page = CreatePropertySheetPageW(&psp);
	if (!page)
	{
		return E_OUTOFMEMORY;
	}

	if (!pfnAddPage(page, lParam))
	{
		DestroyPropertySheetPage(page);
		return E_FAIL;
	}

	this->lpVtbl->AddRef(this); // Release in AttrCallback_PSPCB_RELEASE

	return S_OK;
}

HRESULT STDMETHODCALLTYPE pspeReplacePage(IPunShellPropSheetExt *this, EXPPS uPageID, LPFNSVADDPROPSHEETPAGE pfnReplaceWith, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(uPageID);
	UNREFERENCED_PARAMETER(pfnReplaceWith);
	UNREFERENCED_PARAMETER(lParam);

	return E_NOTIMPL;
}

IPunShellPropSheetExtVtbl g_pspeVtbl = 
{
	pspeQueryInterface,
	pspeAddRef,
	pspeRelease,
	pspeAddPages,
	pspeReplacePage
};

void pseiInit(IPunShellExtInit *this)
{
	this->lpVtbl = &g_pseiVtbl;
	this->m_count = 1;
	this->m_shellPropSheetExt.lpVtbl = &g_pspeVtbl;
	this->m_shellPropSheetExt.m_parent = this;
	this->m_dataObject = NULL;

	this->m_archiveState = CHECKBOXSTATE_BY_INVALID_VALUE;
	this->m_archiveStateNeedChange = FALSE;
	this->m_readonlyState = CHECKBOXSTATE_BY_INVALID_VALUE;
	this->m_readonlyStateNeedChange = FALSE;
	this->m_hiddenState = CHECKBOXSTATE_BY_INVALID_VALUE;
	this->m_hiddenStateNeedChange = FALSE;
	this->m_systemState = CHECKBOXSTATE_BY_INVALID_VALUE;
	this->m_systemStateNeedChange = FALSE;
	memset(&this->m_creationTime, 0, sizeof(SYSTEMTIME));
	this->m_creationTimeNeedChange = FALSE;
	memset(&this->m_creationDate, 0, sizeof(SYSTEMTIME));
	this->m_creationDateNeedChange = FALSE;
	memset(&this->m_lastAccessTime, 0, sizeof(SYSTEMTIME));
	this->m_lastAccessTimeNeedChange = FALSE;
	memset(&this->m_lastAccessDate, 0, sizeof(SYSTEMTIME));
	this->m_lastAccessDateNeedChange = FALSE;
	memset(&this->m_lastWriteTime, 0, sizeof(SYSTEMTIME));
	this->m_lastWriteTimeNeedChange = FALSE;
	memset(&this->m_lastWriteDate, 0, sizeof(SYSTEMTIME));
	this->m_lastWriteDateNeedChange = FALSE;
}

IPunShellExtInit *pseiNew(void)
{
	IPunShellExtInit *result = (IPunShellExtInit *)malloc(sizeof(IPunShellExtInit));
	if (result)
	{
		pseiInit(result);
	}
	return result;
}

// IPunClassFactory

typedef struct IPunClassFactory IPunClassFactory;

typedef struct IPunClassFactoryVtbl // IClassFactoryVtbl
{
	// IUnknown
	HRESULT STDMETHODCALLTYPE (*QueryInterface)(IPunClassFactory *, REFIID, LPVOID *);
	ULONG STDMETHODCALLTYPE (*AddRef)(IPunClassFactory *);
	ULONG STDMETHODCALLTYPE (*Release)(IPunClassFactory *);
	// IClassFactory
	HRESULT STDMETHODCALLTYPE (*CreateInstance)(IPunClassFactory *, LPUNKNOWN, REFIID, LPVOID *);
	HRESULT STDMETHODCALLTYPE (*LockServer)(IPunClassFactory *, BOOL);
} IPunClassFactoryVtbl;

struct IPunClassFactory
{
	IPunClassFactoryVtbl *lpVtbl;
	UINT m_count;
};

HRESULT STDMETHODCALLTYPE pcfQueryInterface(IPunClassFactory *this, REFIID riid, LPVOID *ppv)
{
	HRESULT result;
	if (!ppv)
	{
		result = E_POINTER;
	} else
	if (IsEqualIID(riid, &IID_IUnknown) || 
		IsEqualIID(riid, &IID_IClassFactory))
	{
		*ppv = this;
		this->lpVtbl->AddRef(this);
		result = S_OK;
	} else
	{
		*ppv = NULL;
		result = E_NOINTERFACE;
	}
	return result;
}

ULONG STDMETHODCALLTYPE pcfAddRef(IPunClassFactory *this)
{
	ULONG result = ++this->m_count;
	return result;
}

ULONG STDMETHODCALLTYPE pcfRelease(IPunClassFactory *this)
{
	ULONG result = --this->m_count;
	if (!result)
	{
		free(this);
		--g_objCount;
	}
	return result;
}

HRESULT STDMETHODCALLTYPE pcfCreateInstance(IPunClassFactory *this, LPUNKNOWN pUnkOuter, REFIID riid, LPVOID *ppv)
{
	HRESULT result;
	if (!ppv)
	{
		result = E_POINTER;
	} else
	if (pUnkOuter)
	{
		*ppv = NULL;
		result = CLASS_E_NOAGGREGATION;
	} else 
	{
		IPunShellExtInit *pObj = pseiNew();
		if (pObj)
		{
			result = pObj->lpVtbl->QueryInterface(pObj, riid, ppv);
			if (SUCCEEDED(result))
			{
				++g_objCount;
			}
			pObj->lpVtbl->Release(pObj);
		} else
		{
			*ppv = NULL;
			result = E_OUTOFMEMORY;
		}
	}
	return result;
}

HRESULT STDMETHODCALLTYPE pcfLockServer(IPunClassFactory *this, BOOL lock)
{
	if (lock)
	{
		++g_lock;
	} else
	{
		--g_lock;
	}
	return NOERROR;
}

IPunClassFactoryVtbl g_pcfVtbl = 
{
	pcfQueryInterface,
	pcfAddRef,
	pcfRelease,
	pcfCreateInstance,
	pcfLockServer
};

void pcfInit(IPunClassFactory *this)
{
	this->lpVtbl = &g_pcfVtbl;
	this->m_count = 1;
}

IPunClassFactory *pcfNew(void)
{
	IPunClassFactory *result = (IPunClassFactory *)malloc(sizeof(IPunClassFactory));
	if (result)
	{
		pcfInit(result);
	}
	return result;
}

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppv)
{
	HRESULT result;
	if (!ppv)
	{
		result = E_POINTER;
	} else
	{
		if (IsEqualCLSID(rclsid, &CLSID_IPun))
		{
			IPunClassFactory *pcfObj = pcfNew();
			if (pcfObj)
			{
				result = pcfObj->lpVtbl->QueryInterface(pcfObj, riid, ppv);
				if (SUCCEEDED(result))
				{
					++g_objCount;
				}
				pcfObj->lpVtbl->Release(pcfObj);
			} else
			{
				*ppv = NULL;
				result = E_OUTOFMEMORY;
			}
		} else
		{
			*ppv = NULL;
			result = CLASS_E_CLASSNOTAVAILABLE;
		}
	}
	return result;
}

STDAPI DllCanUnloadNow(void)
{
	return (g_lock | g_objCount)? S_FALSE : S_OK;
}

// For RegInstall
// #include <advpub.h>
// RegInstallW lives in advpack.lib, which is currently not included in the
// Public SDK, though being documented on MSDN.  Instead of linking in the
// library, we can delay load it using LoadLibrary
typedef HRESULT WINAPI (*REGINSTALLW)(HMODULE, LPCWSTR, /*LPCSTRTABLE*/void *);

STDAPI DllRegisterServer(void)
{
	HRESULT result = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	HMODULE lib = LoadLibraryW(L"advpack.dll");
	if (lib)
	{
		REGINSTALLW RegInstallW = (REGINSTALLW)GetProcAddress(lib, "RegInstallW");
		if (RegInstallW)
		{
			result = RegInstallW(g_instDLL, L"RegDll", NULL);
		}
		FreeLibrary(lib);
	}
	return result;
}

STDAPI DllUnregisterServer(void)
{
	HRESULT result = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
	HMODULE lib = LoadLibraryW(L"advpack.dll");
	if (lib)
	{
		REGINSTALLW RegInstallW = (REGINSTALLW)GetProcAddress(lib, "RegInstallW");
		if (RegInstallW)
		{
			result = RegInstallW(g_instDLL, L"UnregDll", NULL);
		}
		FreeLibrary(lib);
	}
	return result;
}

BOOL APIENTRY DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
	switch (fdwReason)
	{
		case DLL_PROCESS_ATTACH:
			g_instDLL = hInstDLL;
			DisableThreadLibraryCalls(hInstDLL);
		break;
	}
	return TRUE;
}
